package ${domain}.frame.utils;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Stack;

/**
 * ClassUtil类工具
 *
 * @author wangbing
 * @version 0.0.1
 * @since 2017-01-01
 */
public class ClassUtil {

    /**
     * 获取类的所有字段(不包含父类)
     *
     * @param clazz 类对象
     * @return 字段集合
     */
    public static Field[] getFields(Class clazz) {
        return getFields(clazz, false);
    }

    /**
     * 获取类所有字段
     *
     * @param clazz       类对象
     * @param parentFirst 是否关注父类的字段
     * @return 字段对象
     */
    public static Field[] getFields(Class clazz, boolean parentFirst) {
        Field[] fields = clazz.getDeclaredFields();

        if (parentFirst) {
            Field[] parentFields = getParentFields(clazz.getSuperclass());
            return ArrayUtil.merge(fields, parentFields);
        }
        return fields;
    }

    /**
     * 获取父类字段集合
     *
     * @param clazz 类对象
     * @return
     */
    private static Field[] getParentFields(Class clazz) {
        if (clazz != null && clazz.getSuperclass() != null) {
            Field[] pfs = clazz.getSuperclass().getDeclaredFields();
            Field[] ppfs = getParentFields(clazz.getSuperclass());
            return ArrayUtil.merge(pfs, ppfs);
        }
        return new Field[0];
    }

    /**
     * 判断是不是集合的实现类
     *
     * @param clazz 类对象
     * @return 是否
     */
    public static boolean isCollection(Class<?> clazz) {
        return Collection.class.isAssignableFrom(clazz);
    }

    /**
     * 获取GET方法
     *
     * @param name      成员变量名
     * @param pojoClass 类对象
     * @return 方法对象
     */
    public static Method getMethod(String name, Class<?> pojoClass) {
        String getMethodName = "get" + StringUtil.upperFirstWord(name);
        try {
            return pojoClass.getMethod(getMethodName);
        } catch (Exception e) {
            getMethodName = "is" + StringUtil.upperFirstWord(name);
            try {
                return pojoClass.getMethod(getMethodName);
            } catch (NoSuchMethodException e1) {
                throw new RuntimeException("can not find[" + name + "]method");
            }
        }
    }

    /**
     * 获取SET方法
     *
     * @param name      成员变量名
     * @param pojoClass POJO对象
     * @return 方法对象
     */
    public static Method setMethod(String name, Class<?> pojoClass, Class<?> type) {
        String setMethodName = "set" + StringUtil.upperFirstWord(name);
        try {
            return pojoClass.getMethod(setMethodName, type);
        } catch (Exception e) {
            throw new RuntimeException("can not find[" + name + "]method");
        }
    }

    /**
     * 判断字段是否为自定义类
     *
     * @param field 字段
     * @return 是否
     */
    public static boolean isJavaClass(Field field) {
        Class<?> fieldType = field.getType();
        boolean isBaseClass = false;
        if (fieldType.isArray()) {
            isBaseClass = false;
        } else if (fieldType.isPrimitive() || fieldType.getPackage() == null
                || fieldType.getPackage().getName().equals("java.lang")
                || fieldType.getPackage().getName().equals("java.math")
                || fieldType.getPackage().getName().equals("java.sql")
                || fieldType.getPackage().getName().equals("java.util")) {
            isBaseClass = true;
        }
        return isBaseClass;
    }

    /**
     * @param sourcePath 源文件路径
     * @param classPath  编译文件路径
     * @return 是否成功
     */
    public static boolean compile(String sourcePath, String classPath) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable<String> options = Arrays.asList("-d", classPath);
        List<File> fileList = listJavaFile(sourcePath);
        File[] files = new File[fileList.size()];
        fileList.toArray(files);
        Iterable<? extends JavaFileObject> javaFileObjects = fileManager.getJavaFileObjects(files);
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, null, javaFileObjects);
        return task.call();
    }

    /**
     * @param className    类名
     * @param classContent 类内容
     * @param classPath    编译路径
     * @return 是否编译成功
     */
    public static boolean compileStringJava(String className, String classContent, String classPath) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable<String> options = Arrays.asList("-d", classPath);
        SimpleJavaFileObject simpleJavaFileObject = new SimpleJavaFileObject(
                URI.create("string:///" + className.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension),
                JavaFileObject.Kind.SOURCE) {
            @Override
            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
                return classContent;
            }
        };
        Iterable<JavaFileObject> simpleJavaFileObjects = Arrays.asList(simpleJavaFileObject);
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, null, simpleJavaFileObjects);
        return task.call();
    }

    private static List<File> listJavaFile(String sourcePath) {
        ArrayList<File> result = new ArrayList<>();
        File file = new File(sourcePath);
        if (!file.exists()) file.mkdirs();

        for (File item : file.listFiles()) {
            if (item.isFile() && item.getName().endsWith(".java")) {
                result.add(item);
            } else if (item.isDirectory()) {
                result.addAll(listJavaFile(item.getAbsolutePath()));
            }
        }
        return result;
    }

    public static void loadClass(String classPath) throws Exception {
        loadClass(new File(classPath));
    }

    /**
     * 加载指定clazzPath
     * <p>
     * 例如clazzPath = /classes class文件的实际位置是/classes/text/test.class
     *
     * @param classPath
     * @throws Exception
     */
    public static void loadClass(File classPath) throws Exception {
        // 记录加载.class文件的数量
        int clazzCount = 0;
        //only handle the folder
        if (classPath.isFile()) {
            classPath = classPath.getParentFile();
        }

        if (classPath.exists() && classPath.isDirectory()) {
            // 获取路径长度
            int classPathLen = classPath.getAbsolutePath().length() + 1;

            Stack<File> stack = new Stack<>();
            stack.push(classPath);

            // 遍历类路径
            while (stack.isEmpty() == false) {
                File path = stack.pop();
                File[] classFiles = path.listFiles(new FileFilter() {
                    public boolean accept(File pathname) {
                        return pathname.isDirectory() || pathname.getName().endsWith(".class");
                    }
                });
                for (File subFile : classFiles) {
                    if (subFile.isDirectory()) {
                        stack.push(subFile);
                    } else {
                        if (clazzCount++ == 0) {
                            Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
                            boolean accessible = method.isAccessible();
                            try {
                                if (accessible == false) {
                                    method.setAccessible(true);
                                }
                                // 设置类加载器
                                URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
                                // 将当前类路径加入到类加载器中
                                method.invoke(classLoader, classPath.toURI().toURL());
                            } finally {
                                method.setAccessible(accessible);
                            }
                        }
                        // 文件名称
                        String className = subFile.getAbsolutePath();
                        className = className.substring(classPathLen, className.length() - 6);
                        className = className.replace(File.separatorChar, '.');
                        // 加载Class类
                        Class.forName(className);
                        System.out.println(String.format("读取应用程序类文件[class=%s]", className));
                    }
                }
            }
        }
    }

    /**
     * 获取指定类
     * 获取之前请确认是否加载 {@link #loadClass(File)}
     * 与Class.forName()相比，两者都可以装载类，但如果程序依赖于Class是否被初始化，就必须用Class.forName(name)了。
     *
     * @param className 类名(包含package,不含后缀)
     * @return 类
     * @throws Exception 获取类异常
     */
    public static Class<?> getClass(String className) throws Exception {
        ClassLoader loader = ClassUtil.class.getClassLoader();
        return loader.loadClass(className);
    }
}
